/*
 * ZFreq.c by CyrIng
 *
 * Copyright (C) 2014 CYRIL INGENIERIE
 * Licenses: GPL2
 */

#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <pthread.h>

#include "zfreq.h"

#define	ACTIVTY "##################################################"
#define	BUMP	"****************"
const char CLS[6+1]={27,'[','H',27,'[','J',0};

short int flag=0xff;

static void *ZFreq_threadfn(void *data)
{
	PROCESSOR *Proc=(PROCESSOR *) data;
	register int cpu;

	while(flag)
	{
		// Frequency activities char strings.
		char activity[50]={0}, bump[4*4]={0};

		// Settle in microseconds.
		usleep(Proc->idleTime * 10000);
		// Print the header.
		printf("%sProcessor [%s]\n\nCore# @ %5d MHz   Operating    %% C0   %% C3   %% C6   Temp.°C    Usage\n", CLS, Proc->Features.BrandString, 150);

		for(cpu=0; cpu < Proc->Features.ThreadCount; cpu++)
		{
				// Compute Delta of Unhalted (Core & Ref) Cycles = Current[1] - Previous[0]
				unsigned long long	UnhaltedCoreCycles	= Proc->Core[cpu].UnhaltedCoreCycles[1]
										- Proc->Core[cpu].UnhaltedCoreCycles[0],
							UnhaltedRefCycles	= Proc->Core[cpu].UnhaltedRefCycles[1]
										- Proc->Core[cpu].UnhaltedRefCycles[0],
							DeltaC3RefCycles	= Proc->Core[cpu].RefCycles.C3[1]
										- Proc->Core[cpu].RefCycles.C3[0],
							DeltaC6RefCycles	= Proc->Core[cpu].RefCycles.C6[1]
										- Proc->Core[cpu].RefCycles.C6[0];
				Proc->Core[cpu].DeltaTSC=Proc->Core[cpu].TSC[1] - Proc->Core[cpu].TSC[0];
				// Compute C-State.
				Proc->Core[cpu].State.C0=(double) (UnhaltedRefCycles) / (double) (Proc->Core[cpu].DeltaTSC);
				Proc->Core[cpu].State.C3=(double) (DeltaC3RefCycles) / (double) (Proc->Core[cpu].DeltaTSC);
				Proc->Core[cpu].State.C6=(double) (DeltaC6RefCycles) / (double) (Proc->Core[cpu].DeltaTSC);
				// Compute the Current Core Ratio per Cycles Delta. Set with constant to protect against a division by zero.
				Proc->Core[cpu].UnhaltedRatio=(UnhaltedRefCycles != 0) ?
					(Proc->Platform.MaxNonTurboRatio * UnhaltedCoreCycles) / UnhaltedRefCycles : 0;
				// Actual Frequency = Current Ratio x Bus Clock Frequency
                                Proc->Core[cpu].UnhaltedFreq=Proc->Core[cpu].UnhaltedRatio * 150; //P.Bios.ClockSpeed;
                                // Temperature = Thermal Junction Max - Digital Thermal Sensor
                                Proc->Core[cpu].Temp=Proc->Core[cpu].TjMax.Target - Proc->Core[cpu].Therm.DTS;

				// Print the activities; append with asterisks : one per turbo bump.
				unsigned long long ActivityRatio=(unsigned long long) (Proc->Core[cpu].UnhaltedRatio * Proc->Core[cpu].State.C0);
				strncpy(activity, ACTIVTY, ActivityRatio);
				activity[ActivityRatio]='\0';
				if(Proc->Core[cpu].UnhaltedRatio > Proc->Core[cpu].Operating.Ratio)
				{
					int qb=Proc->Core[cpu].UnhaltedRatio - Proc->Core[cpu].Operating.Ratio;
					strncpy(bump, BUMP, qb); bump[qb]='\0';
					strcat(activity, bump);
				}
				printf("% 2llu %4llu [%2llu] {%2lld @ %3d%%}  %6.2f %6.2f %6.2f  [%3llu ]:%s\n", \
						cpu,
						Proc->Core[cpu].UnhaltedFreq,
						Proc->Core[cpu].UnhaltedRatio,
						Proc->Core[cpu].Operating.Ratio,
						(100 * UnhaltedCoreCycles) / UnhaltedRefCycles,
						100 * Proc->Core[cpu].State.C0,
						100 * Proc->Core[cpu].State.C3,
						100 * Proc->Core[cpu].State.C6,
						Proc->Core[cpu].Temp,
						activity);
		}
		fflush(stdout);
	}
	return(NULL);
}

const struct {
	struct	SIGNATURE	Signature;
	struct	ADDR {
		int		TURBO_RATIO_LIMIT,
				CORE_C3_RESIDENCY,
				CORE_C6_RESIDENCY,
				TEMPERATURE_TARGET;
	} MSR;
		int		ClockSpeed;
} ARCH[ARCHITECTURES]={
				{ Nehalem_CPUID_SIGNATURE,  0x1ad, 0x3fc, 0x3fd, 0x1a2, Nehalem_BASE_CLOCK },
				{ Sandy_1G_CPUID_SIGNATURE, 0x1ad, 0x3fc, 0x3fd, 0x1a2, Sandy_BASE_CLOCK   },
				{ Sandy_2G_CPUID_SIGNATURE, 0x1ad, 0x3fc, 0x3fd, 0x1a2, Sandy_BASE_CLOCK   },
				{ Ivy_CPUID_SIGNATURE,      0x1ad, 0x3fc, 0x3fd, 0x1a2, Ivy_BASE_CLOCK     },
				{ Haswell_3C_CPUID_SIGNATURE, 0x1ad, 0x3fc, 0x3fd, 0x1a2, Haswell_BASE_CLOCK },
				{ Haswell_45_CPUID_SIGNATURE, 0x1ad, 0x3fc, 0x3fd, 0x1a2, Haswell_BASE_CLOCK },
				{ Haswell_46_CPUID_SIGNATURE, 0x1ad, 0x3fc, 0x3fd, 0x1a2, Haswell_BASE_CLOCK },
			};


int main(int argc, char *argv[]) {
	PROCESSOR *Proc=NULL;
	pthread_t TID;
	int  fd=open(SHM_FILENAME, O_RDWR);

	// Root ID is a prerequisite
	if((geteuid() == 0) && (argc <= 3) && (fd != -1))
	{
		// Micro seconds delay between iterations.
/*		const useconds_t idleTime=(argc > 2) ? atoi(argv[2]) : 1000000;
		// Working variables & structures.
		register unsigned long long cpu;, ThreadCount;
*/
		// Find Processor Architecture.
		for(Proc->ArchID=ARCHITECTURES; Proc->ArchID >=0 ; Proc->ArchID--)
				if(!(ARCH[Proc->ArchID].Signature.ExtFamily ^ Proc->Features.Std.AX.ExtFamily)
				&& !(ARCH[Proc->ArchID].Signature.Family ^ Proc->Features.Std.AX.Family)
				&& !(ARCH[Proc->ArchID].Signature.ExtModel ^ Proc->Features.Std.AX.ExtModel)
				&& !(ARCH[Proc->ArchID].Signature.Model ^ Proc->Features.Std.AX.Model))
					break;
		/* Open SMBIOS gate.
		char	pathname[]="/sys/firmware/dmi/entries/999-99/raw";
		sprintf(pathname, "/sys/firmware/dmi/entries/%d-%d/raw", SMBIOS_PROCINFO_STRUCTURE, SMBIOS_PROCINFO_INSTANCE);
		ssize_t retval;

		if( (P.Bios.FD=open(pathname, O_RDONLY)) != -1 ) {
			// Bus Clock Frequency
			retval=pread(P.Bios.FD, &P.Bios.ClockSpeed,  1, SMBIOS_PROCINFO_EXTCLK);
			// Number of BIOS activated logical Cores
			retval=pread(P.Bios.FD, &P.Bios.ThreadCount, 1, SMBIOS_PROCINFO_THREADS);
			close(P.Bios.FD);
		}*/
		/* Fallback to factory bus clock frequency.
		if(!P.Bios.ClockSpeed)
			if(P.ArchID >0)
				P.Bios.ClockSpeed=ARCH[P.ArchID].ClockSpeed;
			else
				exit(1);*/
		/* Fallback to CPUID fixed count of threads if unavailable from BIOS.
		ThreadCount=(P.Bios.ThreadCount > 0) ? P.Bios.ThreadCount : P.Features.ThreadCount;*/

		Proc=mmap(NULL, sizeof(PROCESSOR), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x0);
		if(Proc != NULL)
		{
			struct termios oldt, newt;
			tcgetattr ( STDIN_FILENO, &oldt );
			newt = oldt;
			newt.c_lflag &= ~( ICANON | ECHO );
			newt.c_cc[VTIME] = 0;
			newt.c_cc[VMIN] = 1;
			tcsetattr ( STDIN_FILENO, TCSANOW, &newt );

			if(!pthread_create(&TID, NULL, ZFreq_threadfn, Proc))
			{
				int key=0;
				while(flag)
				{
					key=getchar();
					switch(key)
					{
						case 'u': if(Proc->idleTime < LOOP_MAX_MS) Proc->idleTime+=10;
						break;
						case 'd': if(Proc->idleTime > LOOP_MIN_MS) Proc->idleTime-=10;
						break;
						case 'x': flag=0;
						break;
					}
				}
				pthread_join(TID, NULL);
			}
			tcsetattr ( STDIN_FILENO, TCSANOW, &oldt );
			printf("\n");

			munmap(Proc, sizeof(PROCESSOR));
		}
		else
			printf("ZFreq: mmap(fd:%d):KO\n", fd);

		close(fd);
	}
	else
		printf("ZFreq: run as root: %s [#loops] [idle (msec)]\n", argv[0]);
}
